using System;
using System.Collections.Generic;
using System.Text;

using Pegasus.Data.SqlClient;
using Pegasus.Diagnostics;
using Pegasus.Log4Net;

namespace Pegasus.Threading
{
    /// <remarks>
    /// This class depends on the SQLAppScheduler2Script
    /// </remarks>
    public class SQLAppScheduler2 : AppScheduler2
    {
		// Local Instance Values
		private ILog m_log = LogManager.GetLogger( typeof( SQLAppScheduler2 ) );
		private SqlDatabaseConnection m_databaseConn = null;

		// Local Const Values
		private const string TABLE_NAME = "AppSchedulerLocks";
        private const string Proc_AcquireTaskLock = "SchedulerAcquireTaskLock";
        private const string Proc_AddTaskLock = "SchedulerAddTaskLock";
        private const string Proc_DeleteTaskLock = "SchedulerDeleteTaskLock";
        private const string Proc_ReleaseAllLocks = "SchedulerReleaseAllLocks";
        private const string Proc_ReleaseTaskLock = "SchedulerReleaseTaskLock";

		/// <summary>
		/// Initializes a new instance of the <see cref="SQLAppScheduler2"/> class.
		/// </summary>
		/// <param name="connection">The connection.</param>
        public SQLAppScheduler2( SqlDatabaseConnection connection )
        {
            m_databaseConn = connection;
            init();
        }

		/// <summary>
		/// Schedules the task.
		/// </summary>
		/// <param name="task">The task.</param>
        public override void ScheduleTask( IScheduleTask task )
        {
            addDatabaseLock( task.Name );

            base.ScheduleTask( task );
        }

		/// <summary>
		/// Tasks the thread proc.
		/// </summary>
		/// <param name="state">The state.</param>
        protected override void TaskThreadProc( object state )
        {
            IScheduleTask task = (IScheduleTask) state;

            if( getDatabaseLock( task.Name ) )
            {
                try
                {
                    task.RunTask();
                }
                catch( Exception e )
                {
                    m_log.Error( string.Format( "Unhandled Exception in task {0}", task.Name ), e );
                }
                finally
                {
                    if( task.ReoccurringTask )
                    {
                        releaseDatabaseLock(task.Name);
                    }
                    else
                    {
                        deleteDatabaseLock(task.Name);
                    }
                }
            }

            // Re-schedule task if reoccuring and not already scheduled.
            if( task.ReoccurringTask && !IsScheduledTask( task ) )
            {
                ScheduleTask( task );
            }
        }

        private void addDatabaseLock( string taskName )
        {
            using( SqlDatabaseAccess access = m_databaseConn.CreateDatabaseAccess() )
            {
                access.StoredProcNonQuery( Proc_AddTaskLock, taskName );
            }
        }

        private void deleteDatabaseLock( string taskName )
        {
            using( SqlDatabaseAccess access = m_databaseConn.CreateDatabaseAccess() )
            {
                access.StoredProcNonQuery( Proc_DeleteTaskLock, taskName, Environment.MachineName );
            }
        }

        private bool getDatabaseLock( string taskName )
        {
            int recordCount = -1;

            using( SqlDatabaseAccess access = m_databaseConn.CreateDatabaseAccess() )
            {
                recordCount = (int) access.StoredProcWithReturn( Proc_AcquireTaskLock, taskName, Environment.MachineName );
            }

            if( recordCount == 0 )
            {
                return false;
            }
            else if( recordCount == 1 )
            {
                return true;
            }

            throw new Exception( string.Format( "Invalid response code in getDatabaseLock for task name {0}, returned {1}, expected 0 or 1.",
                taskName, recordCount ) );
        }

        private void init()
        {
            // Check local instance values
            ParamCode.AssertNotNull( m_databaseConn, "m_databaseConn" );

            // Clear existing locks for the given machine
            using( SqlDatabaseAccess access = m_databaseConn.CreateDatabaseAccess() )
            {
                access.StoredProcNonQuery( Proc_ReleaseAllLocks, Environment.MachineName );
            }
        }

        private void releaseDatabaseLock( string taskName )
        {
            using( SqlDatabaseAccess access = m_databaseConn.CreateDatabaseAccess() )
            {
                access.StoredProcNonQuery( Proc_ReleaseTaskLock, taskName, Environment.MachineName );
            }
        }
    }
}
